/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { ts, LineColPosition, AstTreeUtils, ArkFile } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import {
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
} from "../../matcher/Matchers";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { Utils } from "../../utils/common/Utils";
import { RuleFix } from "../../model/Fix";
//methodParams
type Options = {
  allowPrivateClassPropertyAccess: boolean;
  allowProtectedClassPropertyAccess: boolean;
  allowIndexSignaturePropertyAccess: boolean;
  allowKeywords: boolean;
  allowPattern: string;
};

const defaultOptions1: Options = {
  allowPrivateClassPropertyAccess: false,
  allowProtectedClassPropertyAccess: false, //默认fail
  allowIndexSignaturePropertyAccess: false, //默认fail
  allowKeywords: true,
  allowPattern: "",
};

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, "DotNotationCheck");
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: "docs/dot-notation-check.md",
  description: "is better written in dot notation.",
};
//要求或不允许函数标识符和它们的调用之间有空格
export class DotNotationCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private issueMap: Map<string, IssueReport> = new Map();
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatchBuildCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check,
    };
    return [fileMatchBuildCb];
  }

  public check = (targetFile: ArkFile) => {
    const filePath = targetFile.getName();
    const isTs = this.getFileExtension(filePath, "ts");
    if (!isTs) {
      return;
    }
    let options = (this.rule.option[0] as Options) || defaultOptions1;
    let mergedOptions: Options = {
      ...defaultOptions1,
      ...options,
    };
    this.issueMap.clear();
    const program = ts.createProgram([targetFile.getFilePath()], {
      options: {},
      projectReferences: [],
    });
    let astRoot = AstTreeUtils.getASTNode(
      targetFile.getName(),
      targetFile.getCode()
    );
    for (let child of astRoot.statements) {
      this.loopNode(targetFile, astRoot, child, mergedOptions, program);
    }
    this.reportSortedIssues();
  };

  public loopNode(
    targetFile: ArkFile,
    sourceFile: ts.SourceFile,
    aNode: ts.Node,
    mergedOptions: Options,
    program: ts.Program
  ) {
    const allowKeywords = mergedOptions.allowKeywords;
    const allowPrivateClassPropertyAccess =
      mergedOptions.allowPrivateClassPropertyAccess;
    const allowProtectedClassPropertyAccess =
      mergedOptions.allowProtectedClassPropertyAccess;
    const allowIndexSignaturePropertyAccess =
      mergedOptions.allowIndexSignaturePropertyAccess;
    const children = aNode.getChildren();

    for (const child of children) {
      //1.是[]调用
      if (ts.isElementAccessExpression(child)) {
        const childr = child.getChildren(); //获取调用属性节点  a[name]或者a.name   ----》name
        //获取[和]的node
        let openBracketTokenNode = null;
        let closeBracketTokenNode = null;
        if (childr.length > 3) {
          openBracketTokenNode = childr[1]; // '['
          closeBracketTokenNode = childr[3]; // ']'
        } else {
          logger.error("Invalid child array length:", childr.length);
        }

        const propertyNode = this.getFirstAndLastNode(childr);
        //判断类是否签名索引
        if (propertyNode && propertyNode.lastNode) {
          //1.判断属性的modifier类型
          const propertySymbol = program
            .getTypeChecker()
            .getSymbolAtLocation(propertyNode.lastNode);
          const declareNode = propertySymbol?.getDeclarations()?.[0];
          const modifirs =
            declareNode && ts.canHaveModifiers(declareNode)
              ? ts.getModifiers(declareNode)
              : undefined;
          const modifierKind = modifirs?.[0].kind;
          const fullText = propertyNode.lastNode.getFullText();
          const timText = propertyNode.lastNode.getText().replace(/['"]/g, "");
          let linColstart: LineColPosition = new LineColPosition(-1, -1);
          let linColEnd: LineColPosition = new LineColPosition(-1, -1);
          if (
            propertyNode.lastIndex >= 0 &&
            propertyNode.lastIndex < childr.length &&
            propertyNode.firstIndex < propertyNode.lastIndex
          ) {
            linColstart = LineColPosition.buildFromNode(
              childr[propertyNode.lastIndex - 1],
              sourceFile
            ); // 获取 [ 的位置
            linColEnd = LineColPosition.buildFromNode(
              childr[propertyNode.lastIndex + 1],
              sourceFile
            ); //]的节点位置信息
          }
          const validate = new Set<ts.Node>();
          const decareNode = this.getdeclareNode(
            propertyNode.firstNode,
            program
          );
          const regex = new RegExp(mergedOptions.allowPattern);
          if (
            allowKeywords &&
            (fullText.includes("'") || fullText.includes('"'))
          ) {
            if (
              allowPrivateClassPropertyAccess &&
              modifierKind &&
              modifierKind !== ts.SyntaxKind.PrivateKeyword
            ) {
              continue;
            } else if (
              allowProtectedClassPropertyAccess &&
              modifierKind &&
              modifierKind == ts.SyntaxKind.ProtectedKeyword
            ) {
              continue;
            } else if (
              allowIndexSignaturePropertyAccess &&
              modifierKind === undefined &&
              propertyNode.firstNode &&
              this.checkForIndexSignature(decareNode, program, validate)
            ) {
              continue;
            } else if (
              ts.isStringLiteral(propertyNode.lastNode) &&
              !(mergedOptions.allowPattern != "" && regex.test(timText))
            ) {
              const defect = this.addIssueReport(
                targetFile,
                linColstart.getLineNo(),
                linColstart.getColNo(),
                linColstart.getColNo() + 1,
                "[" + fullText + "]",
                "[" + fullText + "] " + this.metaData.description
              );
              //添加fiX
              if (closeBracketTokenNode && openBracketTokenNode) {
                let fix: RuleFix = this.ruleFix(
                  openBracketTokenNode.getStart(),
                  closeBracketTokenNode.getEnd(),
                  `.${timText}`
                );
                this.issueMap.set(defect.fixKey, { defect, fix });
              }
            }
          }
        }
      } else if (ts.isPropertyAccessExpression(child)) {
        const childr = child.getChildren(); //获取调用属性节点  a[name]或者a.name   ----》name
        const propertyNode = this.getFirstAndLastNode(childr);

        if (propertyNode && propertyNode.lastNode) {
          const fullText = propertyNode.lastNode.getFullText();
          const linCol = LineColPosition.buildFromNode(
            propertyNode.lastNode,
            sourceFile
          );
          const propertyName = propertyNode.lastNode?.getText() ?? "";
          if (!allowKeywords && this.isKeyword(propertyName)) {
            //4.添加报错
            const defect = this.addIssueReport(
              targetFile,
              linCol.getLineNo(),
              linCol.getColNo(),
              linCol.getColNo(),
              "." + fullText,
              "." + fullText + this.metaData.description
            );
            let fix: RuleFix = this.ruleFix(
              propertyNode.lastNode.getStart(),
              propertyNode.lastNode.getEnd(),
              `["${fullText}"]`
            );
            this.issueMap.set(defect.fixKey, { defect, fix });
          }
        }
      }

      this.loopNode(targetFile, sourceFile, child, mergedOptions, program);
    }
  }
  //获取对象属性节点
  private getFirstAndLastNode(childr: ts.Node[]): {
    firstNode: ts.Node | undefined;
    lastNode: ts.Node | undefined;
    firstIndex: number;
    lastIndex: number;
  } {
    let firstNode: ts.Node | undefined;
    let lastNode: ts.Node | undefined;
    let firstNodeIndex: number = -1;
    let lastNodeIndex: number = -1;
    // 遍历 childr 数组，筛选出符合条件的节点
    for (let i = 0; i < childr.length; i++) {
      const ch = childr[i];
      if (ts.isIdentifier(ch) || ts.isStringLiteral(ch)) {
        // 如果是第一个符合条件的节点，则保存
        if (firstNode === undefined) {
          firstNode = ch;
          firstNodeIndex = i;
        }
        // 每次找到符合条件的节点，都更新最后一个节点
        lastNode = ch;
        lastNodeIndex = i;
      }
    }

    // 返回首个和最后一个符合条件的节点
    return {
      firstNode,
      lastNode,
      firstIndex: firstNodeIndex,
      lastIndex: lastNodeIndex,
    };
  }

  //辅助函数
  private checkForIndexSignature(
    childr: ts.Node | undefined,
    program: ts.Program,
    visitedNodes: Set<ts.Node>,
    depth: number = 0,
    count: { total: number } = { total: 0 }
  ): boolean {
    // 如果当前节点为空，则返回 false
    if (!childr) {
      return false;
    }

    // 如果节点已经访问过，则跳过
    if (visitedNodes.has(childr)) {
      return false;
    }

    // 记录当前遍历
    visitedNodes.add(childr);
    count.total++; // 增加遍历次数

    // 输出当前递归的深度

    // 获取当前节点的子节点
    const cdf = childr.getChildren() ?? [];

    // 遍历子节点
    for (const node of cdf) {
      // 获取当前节点的符号和声明
      const propertySymbolq = program
        .getTypeChecker()
        .getSymbolAtLocation(node);
      const cvb = propertySymbolq?.declarations?.[0];

      if (cvb === node.parent) {
        continue;
      }

      // 获取声明的子节点
      const ccd = cvb?.getChildren() ?? [];

      // 遍历声明的子节点
      for (const declareNode of ccd) {
        if (this.checkIndexSignature(declareNode)) {
          return true;
        }
      }

      // 递归检查子节点
      if (
        this.checkForIndexSignature(
          node,
          program,
          visitedNodes,
          depth + 1,
          count
        )
      ) {
        return true;
      }
    }

    // 如果没有找到索引签名声明，返回 false
    return false;
  }
  checkIndexSignature(node: ts.Node): boolean {
    // 如果当前节点是索引签名声明，直接返回 true
    if (ts.isIndexSignatureDeclaration(node)) {
      return true;
    }

    // 获取当前节点的子节点
    const children = node.getChildren();

    // 遍历子节点并递归检查
    for (const child of children) {
      // 如果在子节点中找到索引签名声明，立刻返回 true
      if (this.checkIndexSignature(child)) {
        return true;
      }
    }

    // 如果没有找到索引签名声明，返回 false
    return false;
  }

  getdeclareNode(
    childr: ts.Node | undefined,
    program: ts.Program
  ): ts.Node | undefined {
    if (!childr) {
      return undefined;
    }
    const propertySymbol = program.getTypeChecker().getSymbolAtLocation(childr);
    const cvb = propertySymbol?.declarations?.[0] ?? undefined;
    // 如果是类声明
    if (!cvb) {
      return undefined;
    }
    return cvb;
  }
  // 判断一个字符串是否为关键字或操作符
  private isKeyword(str: string) {
    const keywords = [
      // JavaScript 关键字
      "if",
      "else",
      "for",
      "while",
      "switch",
      "case",
      "break",
      "continue",
      "return",
      "function",
      "class",
      "const",
      "let",
      "var",
      "try",
      "catch",
      "finally",
      "throw",
      "new",
      "this",
      "super",
      "import",
      "export",
      "default",
      "void",
      "undefined",
      "null",
      "true",
      "false",
      "await",
      "async",
      "instanceof",
      "typeof",
      "in",
      "debugger",
      "yield",
      "eval",
      "arguments",
      "delete",
      // TypeScript 特有关键字
      "any",
      "unknown",
      "never",
      "type",
      "interface",
      "enum",
      "as",
      "extends",
      "implements",
      "infer",
      "declare",
      "module",
      "namespace",
      "public",
      "private",
      "protected",
      "readonly",
    ];
    return keywords.includes(str);
  }
  private addIssueReport(
    arkFile: ArkFile,
    line: number,
    startCol: number,
    endCol: number,
    name: string,
    message: string
  ): Defects {
    const severity = this.rule.alert ?? this.metaData.severity;
    const filePath = arkFile.getFilePath();
    const fixKey = line + "%" + endCol + "%" + this.rule.ruleId;
    const mergeKey =
      filePath + "%" + fixKey + "%" + message.padEnd(130) + this.rule.ruleId;
    const defect = new Defects(
      line,
      endCol,
      message,
      severity,
      this.rule.ruleId,
      mergeKey,
      this.metaData.ruleDocPath,
      true,
      false,
      false,
      fixKey
    );
    this.defects.push(defect);
    return defect;
  }

  private ruleFix(pos: number, end: number, text: string): RuleFix {
    return { range: [pos, end], text: text };
  }

  private reportSortedIssues(): void {
    if (this.issueMap.size === 0) {
      return;
    }

    const sortedIssues = Array.from(this.issueMap.entries()).sort(
      ([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB)
    );

    this.issues = [];

    sortedIssues.forEach(([_, issue]) => {
      RuleListUtil.push(issue.defect);
      this.issues.push(issue);
    });
  }
  private getFileExtension(filePath: string, filetype: string): boolean {
    // 使用正则表达式匹配文件后缀
    const match = filePath.match(/\.([0-9a-zA-Z]+)$/);

    // 如果匹配到了扩展名，且扩展名等于 filetype，则返回扩展名，否则返回空字符串
    if (match) {
      const extension = match[1];
      return extension === filetype;
    }

    return false;
  }
}
